package cn.zhiquxing;

import cn.zhiquxing.util.ThreadUtil;
import lombok.extern.slf4j.Slf4j;
import org.junit.jupiter.api.Test;

import java.util.concurrent.*;
import java.util.function.BiConsumer;
import java.util.function.BiFunction;
import java.util.function.Function;
import java.util.function.Supplier;


@Slf4j
public class CompletableFutureDemo {
    
    @Test
    public void runAsyncDemo() throws ExecutionException, InterruptedException, TimeoutException {
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                System.out.println(Thread.currentThread().getName() + "runAsyncDemo");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });

        future.get(2, TimeUnit.SECONDS);
    }
    
    
    @Test
    public void runAsyncCallbackDemo()  throws Exception{
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
                log.info("run end");
                throw new RuntimeException("run exception");
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        });
        
        future.whenComplete(new BiConsumer<Void, Throwable>() {
            @Override
            public void accept(Void unused, Throwable throwable) {
                log.info("run complete");
            }
        });
        
        future.exceptionally(new Function<Throwable, Void>() {

            @Override
            public Void apply(Throwable throwable) {
                log.info("run exception");
                return null;
            }
        });
        
        future.get(2, TimeUnit.SECONDS);
    }
    
    
    @Test
    public void threadPoolDemo() throws ExecutionException, InterruptedException, TimeoutException {
        ThreadPoolExecutor pool = ThreadUtil.MixedTargetThreadPoolLazyHolder.EXECUTOR;
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(() -> {
            log.info("run begin");
            long start = System.currentTimeMillis();
            try {
                TimeUnit.SECONDS.sleep(1);
                
                log.info("run end");
                return System.currentTimeMillis() - start;
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, pool);

        Long l = future.get(2, TimeUnit.SECONDS);
        log.info("异步执行耗时: " + l / 1000);
    }
    
    
    @Test
    public void thenApplyDemo() throws ExecutionException, InterruptedException {
        CompletableFuture<Long> future = CompletableFuture.supplyAsync(new Supplier<Long>() {
            @Override
            public Long get() {
                long firstStep = 10L + 10L;
                log.info("firstStep outcome is: " + firstStep);
                return firstStep;
            }
        }).thenApplyAsync(new Function<Long, Long>() {

            @Override
            public Long apply(Long firstStepOutCome) {
                long secondStep = firstStepOutCome * 2;
                log.info("secondStep outcome is: " + secondStep);
                return secondStep;
            }
        });
        long result = future.get();
        log.info("result: " + result);
    }
    
    @Test
    public void thenCombineDemo() throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                Integer firstStep = 10 + 10;
                log.info("firstStep outcome is: " + firstStep);
                return firstStep;
            }
        });
        
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(new Supplier<Integer>() {
            @Override
            public Integer get() {
                int secondStep = 10 + 10;
                log.info("secondStep outcome is: " + secondStep);
                return secondStep;
            }
        });
        
        CompletableFuture<Integer> future3 = future1.thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {

            @Override
            public Integer apply(Integer step1Outcome, Integer step2Outcome) {
                return step1Outcome * step2Outcome;
            }
        });
        
        int result = future3.get();
        log.info("result: " + result);
    }
    
    
    @Test
    public void allOfDemo() throws ExecutionException, InterruptedException {
        CompletableFuture future1 = CompletableFuture.runAsync(() -> System.out.println("run begin1"));
        CompletableFuture future2 = CompletableFuture.runAsync(() -> System.out.println("run begin2"));
        CompletableFuture future3 = CompletableFuture.runAsync(() -> System.out.println("run begin3"));
        CompletableFuture future4 = CompletableFuture.runAsync(() -> System.out.println("run begin4"));
        
        CompletableFuture.allOf(future1, future2, future3, future4).join();
        
    }
}
